"""
Mathematical calculation tools for FastMCP
"""

from fastmcp import FastMCP, Context
from typing import List
import math


class MathTools:
    """Mathematical calculation tools for FastMCP."""

    def __init__(self, mcp: FastMCP):
        self.mcp = mcp

    def register(self):
        """Register all math tools with FastMCP."""
        self.mcp.tool(
            name="add_numbers",
            description="Add two numbers together",
            tags={"math", "calculation", "public"},
        )(self.add_numbers)

        self.mcp.tool(
            name="multiply_numbers",
            description="Multiply two numbers together",
            tags={"math", "calculation", "public"},
        )(self.multiply_numbers)

        self.mcp.tool(
            name="sum_list",
            description="Calculate the sum of a list of numbers",
            tags={"math", "calculation", "list", "public"},
        )(self.sum_list)

        self.mcp.tool(
            name="calculate_statistics",
            description="Calculate basic statistics for a list of numbers",
            tags={"math", "statistics", "analysis", "public"},
        )(self.calculate_statistics)

        self.mcp.tool(
            name="solve_quadratic",
            description="Solve a quadratic equation ax² + bx + c = 0",
            tags={"math", "algebra", "equation", "public"},
        )(self.solve_quadratic)

        self.mcp.tool(
            name="fibonacci_sequence",
            description="Generate the Fibonacci sequence up to n terms",
            tags={"math", "sequence", "fibonacci", "public"},
        )(self.fibonacci_sequence)

    @staticmethod
    def add_numbers(a: float, b: float) -> float:
        """Add two numbers together."""
        return a + b

    @staticmethod
    def multiply_numbers(a: float, b: float) -> float:
        """Multiply two numbers together."""
        return a * b

    @staticmethod
    def sum_list(numbers: List[float]) -> dict:
        """Calculate the sum of a list of numbers."""
        if not numbers:
            return {"error": "Cannot calculate sum of empty list"}

        total = sum(numbers)
        return {"sum": total, "count": len(numbers), "numbers": numbers}

    @staticmethod
    def calculate_statistics(numbers: List[float]) -> dict:
        """Calculate mean, median, mode, and standard deviation for a list of numbers."""
        if not numbers:
            return {"error": "Empty list provided"}

        # Sort numbers for median calculation
        sorted_nums = sorted(numbers)
        n = len(numbers)

        # Mean
        mean = sum(numbers) / n

        # Median
        if n % 2 == 0:
            median = (sorted_nums[n // 2 - 1] + sorted_nums[n // 2]) / 2
        else:
            median = sorted_nums[n // 2]

        # Standard deviation
        variance = sum((x - mean) ** 2 for x in numbers) / n
        std_dev = math.sqrt(variance)

        # Range
        min_val = min(numbers)
        max_val = max(numbers)

        return {
            "count": n,
            "mean": round(mean, 4),
            "median": median,
            "min": min_val,
            "max": max_val,
            "range": max_val - min_val,
            "standard_deviation": round(std_dev, 4),
            "variance": round(variance, 4),
        }

    @staticmethod
    def solve_quadratic(a: float, b: float, c: float) -> dict:
        """Solve a quadratic equation and return the roots."""
        if a == 0:
            if b == 0:
                if c == 0:
                    return {"type": "identity", "message": "Infinite solutions (0 = 0)"}
                else:
                    return {"type": "contradiction", "message": "No solution"}
            else:
                # Linear equation: bx + c = 0
                root = -c / b
                return {"type": "linear", "root": root}

        # Calculate discriminant
        discriminant = b**2 - 4 * a * c

        if discriminant > 0:
            # Two real roots
            root1 = (-b + math.sqrt(discriminant)) / (2 * a)
            root2 = (-b - math.sqrt(discriminant)) / (2 * a)
            return {
                "type": "two_real_roots",
                "root1": round(root1, 6),
                "root2": round(root2, 6),
                "discriminant": discriminant,
            }
        elif discriminant == 0:
            # One real root (repeated)
            root = -b / (2 * a)
            return {
                "type": "one_real_root",
                "root": round(root, 6),
                "discriminant": discriminant,
            }
        else:
            # Complex roots
            real_part = -b / (2 * a)
            imaginary_part = math.sqrt(abs(discriminant)) / (2 * a)
            return {
                "type": "complex_roots",
                "root1": f"{round(real_part, 6)} + {round(imaginary_part, 6)}i",
                "root2": f"{round(real_part, 6)} - {round(imaginary_part, 6)}i",
                "discriminant": discriminant,
            }

    @staticmethod
    async def fibonacci_sequence(n: int, ctx: Context) -> dict:
        """Generate Fibonacci sequence. Reports progress for large sequences."""
        if n <= 0:
            return {"error": "Number of terms must be positive"}

        if n > 100:
            return {"error": "Maximum 100 terms allowed"}

        # Report progress for larger sequences
        if n > 10:
            await ctx.info(f"Generating Fibonacci sequence with {n} terms...")

        sequence = []
        a, b = 0, 1

        for i in range(n):
            if i == 0:
                sequence.append(0)
            elif i == 1:
                sequence.append(1)
            else:
                next_fib = a + b
                sequence.append(next_fib)
                a, b = b, next_fib

            # Report progress for large sequences
            if n > 20 and i % 10 == 0 and i > 0:
                await ctx.report_progress(progress=i, total=n)

        if n > 10:
            await ctx.info("Fibonacci sequence generation completed")

        return {
            "terms": n,
            "sequence": sequence,
            "last_value": sequence[-1] if sequence else 0,
        }
